package gauge;

import java.awt.Dimension;
import java.awt.Image;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;

import javax.imageio.ImageIO;
import javax.swing.JComponent;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import ui.BasicDialGaugeUI;
import ui.Gauge3StateUI;

import model.Bounded3StateRangeModel;
import model.DefaultBounded3StateRangeModel;

public class DialGauge3State extends JComponent implements SwingConstants {
	
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	private static final String uiClassID="Gauge3StateUI";
	
	// The Model
	private Bounded3StateRangeModel model;
	private double minorTickSpacing = 2;
	private double majorTickSpacing = 10;
	
	private Image needleImage;
	private Image background_safe;
	private Image background_caution;
	private Image background_danger;
	
	private boolean drawZoneLines = true; 
	private boolean drawMinorTickMarks =true;
	private boolean drawMajorTickMarks = true;
	private boolean drawLabels = true;
	
	private int minorTickLength = 10; // how long the tick marks are
	private int majorTickLength = 20;
	private int tickDistance = 100;	// how far from center the tick marks are (needle length)
	
	// how far from center to strt drawing labels 
	// try tickDistance + 1/2 width of longest label
	private int labelDistance = 150;  
	
	// needleLength + some
	private int arcDistance = 90;
	private int arcWidth = arcDistance*2;
	private int arcHeight = arcWidth;
	private int labelWidth = 0;
	private float scale = 1;
	
	private float zoneArcThickness = 1;
	
	
	// from JSlider, this change listener listens to changes in the model and forwards them to  listeners of the app
	protected ChangeListener changeListener = new ModelListener();
	// borrowed from JSlider
	/**
     * Only one <code>ChangeEvent</code> is needed per slider instance since the
     * event's only (read-only) state is the source property.  The source
     * of events generated here is always "this". The event is lazily
     * created the first time that an event notification is fired.
     *
     * @see #fireStateChanged
     */
    protected transient ChangeEvent changeEvent = null;
	
	//CONSTRUCTOR
	//only one constructor for now, TODO chain some additional constructors
	public DialGauge3State(double degreeOffset,double degreeRange,double min,double max,
			double majorTickSpacing,double minorTickSpacing,
			double cautionTripPoint,double dangerTripPoint)
	{
		
		model = new DefaultBounded3StateRangeModel(degreeOffset, degreeRange, min, max,
				cautionTripPoint, dangerTripPoint);
		setMinorTickSpacing(minorTickSpacing);
		setMajorTickSpacing(majorTickSpacing);
		setValue(min);
		// add a changeListener to the model
		model.addChangeListener(changeListener);
		// get the background and needle images.
		try {
			needleImage = ImageIO.read(new File("needle2.png"));
			background_safe = ImageIO.read(new File("background2.png"));
			background_danger = ImageIO.read(new File("background2_red.png"));
			background_caution = ImageIO.read(new File("background2_yellow.png"));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		// set spacing
		calculateSizes();
		this.updateUI();
	}
	
	public void calculateSizes()
	{
		arcDistance = (int) (needleImage.getWidth(this) *.90);
		arcWidth = arcDistance*2;
		arcHeight = arcWidth;
		tickDistance = arcDistance+5;
		int labelWidth = 50; 
		labelDistance = tickDistance + majorTickLength + labelWidth/2;
		int size = (int)((labelDistance*2 + labelWidth)*scale);
		this.setPreferredSize(new Dimension(size, size));
	}
	
	public Gauge3StateUI getUI()
	{
		return (Gauge3StateUI)ui;
	}
	
	public void setUI(Gauge3StateUI ui)
	{
		super.setUI(ui);
	}
	 public void updateUI() {
		 if(UIManager.get(getUIClassID()) != null){
	        setUI((Gauge3StateUI)UIManager.getUI(this));
		 } else {
			 setUI(new BasicDialGaugeUI());
		 }
	    }
	 public String getUIClassID() {
	        return uiClassID;
	    }
	

	public double getMinorTickSpacing() {
		return minorTickSpacing;
	}


	public void setMinorTickSpacing(double minorTickSpacing) {
		if(minorTickSpacing <= 0)
			minorTickSpacing = 1;
			//throw new IllegalArgumentException("minorTickSpacing must be >= 0");
		else
			this.minorTickSpacing = minorTickSpacing;
		this.repaint();
	}


	public double getMajorTickSpacing() {
		return majorTickSpacing;
	}


	public void setMajorTickSpacing(double majorTickSpacing) {
		if(majorTickSpacing <= 0)
			majorTickSpacing = 1;
			//throw new IllegalArgumentException("majorTickSpacing must be >=1");
		else
		this.majorTickSpacing = majorTickSpacing;
		this.repaint();
	}
	public Bounded3StateRangeModel getModel()
	{
		return this.model;
	}


	public Image getNeedleImage() {
		return needleImage;
	}


	public void setNeedleImage(Image needleImage) {
		this.needleImage = needleImage;
		calculateSizes();
		this.repaint();
	}


	public Image getBackground_safe() {
		return background_safe;
	}


	public void setBackground_safe(Image background_safe) {
		this.background_safe = background_safe;
	}


	public Image getBackground_caution() {
		return background_caution;
	}


	public void setBackground_caution(Image background_caution) {
		this.background_caution = background_caution;
	}


	public Image getBackground_danger() {
		return background_danger;
	}


	public void setBackground_danger(Image background_danger) {
		this.background_danger = background_danger;
	}


	public boolean isDrawZoneLines() {
		return drawZoneLines;
	}


	public void setDrawZoneLines(boolean drawZoneLines) {
		this.drawZoneLines = drawZoneLines;
	}


	public boolean isDrawMinorTickMarks() {
		return drawMinorTickMarks;
	}


	public void setDrawMinorTickMarks(boolean drawMinorTickMarks) {
		this.drawMinorTickMarks = drawMinorTickMarks;
	}


	public boolean isDrawMajorTickMarks() {
		return drawMajorTickMarks;
	}


	public void setDrawMajorTickMarks(boolean drawMajorTickMarks) {
		this.drawMajorTickMarks = drawMajorTickMarks;
	}


	public boolean isDrawLabels() {
		return drawLabels;
	}


	public void setDrawLabels(boolean drawLabels) {
		this.drawLabels = drawLabels;
	}


	public float getZoneArcThickness() {
		return zoneArcThickness;
	}


	public void setZoneArcThickness(float thickness) {
		this.zoneArcThickness = thickness;
		this.repaint();
	}


	public int getMinorTickLength() {
		return minorTickLength;
	}


	public void setMinorTickLength(int minorTickLength) {
		this.minorTickLength = minorTickLength;
	}


	public int getMajorTickLength() {
		return majorTickLength;
	}


	public void setMajorTickLength(int majorTickLength) {
		this.majorTickLength = majorTickLength;
	}


	public int getTickDistance() {
		return tickDistance;
	}


	public void setTickDistance(int tickDistance) {
		this.tickDistance = tickDistance;
	}


	public int getLabelDistance() {
		return labelDistance;
	}


	public void setLabelDistance(int labelDistance) {
		this.labelDistance = labelDistance;
	}
	public int getArcDistance() {
		return arcDistance;
	}


	public void setArcDistance(int arcDistance) {
		this.arcDistance = arcDistance;
	}


	public int getArcWidth() {
		return arcWidth;
	}


	public void setArcWidth(int arcWidth) {
		this.arcWidth = arcWidth;
	}


	public int getArcHeight() {
		return arcHeight;
	}


	public void setArcHeight(int arcHeight) {
		this.arcHeight = arcHeight;
	}
	
	public void setValue(double value)
	{
	
		model.setValue(value);
		// works for now, but probably should be handled by a propertyChangeListener
		this.repaint(); 
	}
	public double getValue()
	{
		return model.getValue();
	}
	
	// trip points
	public double getLowerCautionTripPoint()
	{
		return model.getLowerCautionTripPoint();
	}
	public void setLowerCautionTripPoint(double value)
	{
		model.setLowerCautionTripPoint(value);
		this.repaint();
	}
	public double getSafeTripPoint()
	{
		return model.getSafeTripPoint();
	}
	public void setSafeTripPoint(double value)
	{
		model.setSafeTripPoint(value);
		this.repaint();
	}
	public double getUpperCautionTripPoint()
	{
		return model.getUpperCautionTripPoint();
	}
	public void setUpperCautionTripPoint(double value)
	{
		model.setUpperCautionTripPoint(value);
		this.repaint();
	}
	public double getUpperDangerTripPoint()
	{
		return model.getUpperDangerTripPoint();
	}
	public void setUpperDangerTripPoint(double value)
	{
		model.setUpperDangerTripPoint(value);
		this.repaint();
	}
	public void setMin(double value)
	{
		model.setMin(value);
		this.setZoneArcThickness(this.getZoneArcThickness());
		this.update(getGraphics());
	}
	public double getMin()
	{
		return model.getMin();
	}
	public void setMax(double value)
	{
		model.setMax(value);
		this.repaint();
	}
	public double getMax()
	{
		return model.getMax();
	}
	public void setDegreeOffset(double value)
	{
		model.setDegreeOffset(value);
		this.repaint();
	}
	public double getDegreeOffset()
	{
		return model.getDegreeOffset();
	}
	public void setDegreeRange(double value)
	{
		model.setDegreeRange(value);
		this.repaint();
	}
	public double getDegreeRange()
	{
		return model.getDegreeRange();
	}
	
	
	
	public float getScale() {
		return scale;
	}

	public void setScale(float scale) {
		this.scale = scale;
	}

	/*
	 * Borrowed from JSlider
	 */
	/**
     * Subclasses that want to handle {@code ChangeEvent}s
     * from the model differently
     * can override this to return
     * an instance of a custom <code>ChangeListener</code> implementation.
     * The default {@code ChangeListener} simply calls the
     * {@code fireStateChanged} method to forward {@code ChangeEvent}s
     * to the {@code ChangeListener}s that have been added directly to the
     * slider.
     * @see #changeListener
     * @see #fireStateChanged
     * @see javax.swing.event.ChangeListener
     * @see javax.swing.BoundedRangeModel
     */
    protected ChangeListener createChangeListener() {
        return new ModelListener();
    }

    /* 
     * Borrowed from JSlider
     */
    /**
     * Adds a ChangeListener to the slider.
     *
     * @param l the ChangeListener to add
     * @see #fireStateChanged
     * @see #removeChangeListener
     */
    public void addChangeListener(ChangeListener l) {
        listenerList.add(ChangeListener.class, l);
    }

    /* borrowed from JSlider 
    /**
     * Removes a ChangeListener from the slider.
     *
     * @param l the ChangeListener to remove
     * @see #fireStateChanged
     * @see #addChangeListener

     */
    public void removeChangeListener(ChangeListener l) {
        listenerList.remove(ChangeListener.class, l);
    }

    /*
     * borrowed from JSlider
     */
    /**
     * Returns an array of all the <code>ChangeListener</code>s added
     * to this JSlider with addChangeListener().
     *
     * @return all of the <code>ChangeListener</code>s added or an empty
     *         array if no listeners have been added
     * @since 1.4
     */
    public ChangeListener[] getChangeListeners() {
        return listenerList.getListeners(ChangeListener.class);
    }
    
    /* borrowed from JSliderier
     * 
     */
	/**
     * Send a {@code ChangeEvent}, whose source is this {@code JSlider}, to
     * all {@code ChangeListener}s that have registered interest in
     * {@code ChangeEvent}s.
     * This method is called each time a {@code ChangeEvent} is received from
     * the model.
     * <p>
     * The event instance is created if necessary, and stored in
     * {@code changeEvent}.
     *
     * @see #addChangeListener
     * @see EventListenerList
     */
    protected void fireStateChanged() {
        Object[] listeners = listenerList.getListenerList();
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i]==ChangeListener.class) {
                if (changeEvent == null) {
                    changeEvent = new ChangeEvent(this);
                }
                ((ChangeListener)listeners[i+1]).stateChanged(changeEvent);
            }
        }
    }

	
	/*
	 * Borrowed from JSlider
	 */
	 /**
	     * We pass Change events along to the listeners with the
	     * the slider (instead of the model itself) as the event source.
	     */
	private class ModelListener implements ChangeListener, Serializable 
	{
		public void stateChanged(ChangeEvent e) 
		{
			
				fireStateChanged();
		}

	}
	
}


	